package simpletestbed;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import simpletestbed.core.SimpleTestBedClient;

public class RemoteSingleThreadTestBed extends RemoteTestBed{
	
	public RemoteSingleThreadTestBed(String configFileName) throws IOException {
		super(configFileName);
	}
	
	public RemoteSingleThreadTestBed(String configFileName, String localBinPath) throws IOException {
		super(configFileName, localBinPath);
	}
	
	@Override
	public TestResult[] execute() {
		try {
			int numMachines = this.remoteServers.size();
			if (numMachines == 0) {
				throw new Error("No remote server configured !");
			}
			
			TestResult[][] results = new TestResult[this.testCases.size()][];
			double[][] runtimes = new double[this.testCases.size()][];
			double[][] memUsages = new double[this.testCases.size()][];
			for (int caseIdx = 0; caseIdx <this.testCases.size(); caseIdx++) {
				results[caseIdx] = new TestResult[this.testCases.get(caseIdx).getTimes()];
				runtimes[caseIdx] = new double[this.testCases.get(caseIdx).getTimes()];
				memUsages[caseIdx] = new double[this.testCases.get(caseIdx).getTimes()];
			}
			
			RemoteMonitor[] monitors = new RemoteMonitor[numMachines];
			int[] monitorCaseIndexes = new int[numMachines];
			int[] monitorTimeIndexes = new int[numMachines];
			for (int machineId=0; machineId<monitors.length; machineId++) {
				monitors[machineId] = null;				
				// Invoke SimpleTestBed client to send command
				String serverHost = this.remoteServers.get(machineId);
				SimpleTestBedClient client = new SimpleTestBedClient(serverHost);
				// Send java classes
				client.execSendClasses("clientcp" + machineId, this.localBinPath);
			}
			
			for (int caseIdx = 0; caseIdx<this.testCases.size(); caseIdx++) {
				TestCase testcase = this.testCases.get(caseIdx);
				for (int timeIdx=0; timeIdx<testcase.getTimes(); timeIdx++) {
					// Find a free machine
					int freeMachineId = -1;
					while (freeMachineId == -1) {
						for (int machineId=0; machineId<monitors.length; machineId++) {
							RemoteMonitor monitor = monitors[machineId];
							if (monitor == null ) {
								freeMachineId = machineId;
								break;
							}
							else {
								if (monitor.isAlive() == false) { // This monitor has finished
									int itsCaseIdx = monitorCaseIndexes[machineId];
									int itsTimeIdx = monitorTimeIndexes[machineId];
									// Collect its results
									results[itsCaseIdx][itsTimeIdx] = monitor.getResult()[0];
									runtimes[itsCaseIdx][itsTimeIdx] = monitor.getRuntime()[0];
									memUsages[itsCaseIdx][itsTimeIdx] = monitor.getMaxMemUsage();
									// Assign this one to be free
									freeMachineId = machineId;
									// Erase this monitor
									monitors[machineId] = null;
									monitorCaseIndexes[machineId] = -1;
									monitorTimeIndexes[machineId] = -1;
									break;
								}
							}
						}
					}
					// Assign this free machine
					List<TestCase> subCases = new ArrayList<TestCase>(1);
					TestCase cloneTestCase = (TestCase)this.testCases.get(caseIdx).clone();
					ShadowSingleTimeTestCase shadowCase = new ShadowSingleTimeTestCase(cloneTestCase);
					subCases.add(shadowCase);
					String serverHost = this.remoteServers.get(freeMachineId);
					monitors[freeMachineId] = new RemoteMonitor(serverHost, freeMachineId, subCases, 
							this.checkMemUsage, this.threadTimeOut);
					monitorCaseIndexes[freeMachineId] = caseIdx;
					monitorTimeIndexes[freeMachineId] = timeIdx;
					monitors[freeMachineId].start();
				}
			}
			
			// Wait for all monitors finish
			for (int machineId=0; machineId<monitors.length; machineId++) {
				RemoteMonitor monitor = monitors[machineId];
				if (monitor == null ) {
					continue;
				}
				monitor.join();
				int itsCaseIdx = monitorCaseIndexes[machineId];
				int itsTimeIdx = monitorTimeIndexes[machineId];
				// Collect its results
				results[itsCaseIdx][itsTimeIdx] = monitor.getResult()[0];
				runtimes[itsCaseIdx][itsTimeIdx] = monitor.getRuntime()[0];
				memUsages[itsCaseIdx][itsTimeIdx] = monitor.getMaxMemUsage();
				// Erase this monitor
				monitors[machineId] = null;
				monitorCaseIndexes[machineId] = -1;
				monitorTimeIndexes[machineId] = -1;
			}
			
			// Compute the average results
			TestResult[] averageResults = new TestResult[this.testCases.size()];
			this.avgRuntimes = new double[this.testCases.size()];
			this.maxMemUsage = new double[this.testCases.size()];
			for (int caseIdx=0; caseIdx<this.testCases.size(); caseIdx++) {
				this.avgRuntimes[caseIdx] = 0;
				this.maxMemUsage[caseIdx] = 0;
				TestCase testcase = this.testCases.get(caseIdx);
				for (int timeIdx=0; timeIdx<testcase.getTimes(); timeIdx++) {
					this.avgRuntimes[caseIdx] += runtimes[caseIdx][timeIdx];
					this.maxMemUsage[caseIdx] += memUsages[caseIdx][timeIdx];
				}
				averageResults[caseIdx] = testcase.reduceResult(results[caseIdx]);
				this.avgRuntimes[caseIdx] /= testcase.getTimes();
				this.maxMemUsage[caseIdx] /= testcase.getTimes();
			}
			return averageResults;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	
	/**
	 * The shadow test case which can reset the times of running of original test case
	 * @author ltang002
	 *
	 */
	static class ShadowSingleTimeTestCase implements TestCase {

		/**
		 * 
		 */
		private static final long serialVersionUID = -990237503051055563L;
		
		private TestCase testcase = null;
		
		public ShadowSingleTimeTestCase(TestCase oriTestCase) {
			this.testcase = oriTestCase;
		}

		@Override
		public TestResult run() {
			// TODO Auto-generated method stub
			return this.testcase.run();
		}

		@Override
		public String getName() {
			// TODO Auto-generated method stub
			return this.testcase.getName();
		}

		@Override
		public int getTimes() {
			// TODO Auto-generated method stub
			return 1;
		}

		@Override
		public Object clone() {
			// TODO Auto-generated method stub
			return new ShadowSingleTimeTestCase((TestCase)this.testcase.clone());
		}

		@Override
		public TestResult reduceResult(TestResult[] results) {
			// TODO Auto-generated method stub
			return this.testcase.reduceResult(results);
		}
		
	}
	


}
